package com.tjbknb.ecui;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Toast;


public class ECUIService extends Service{

	private static final String TAG = "Service";
	static FreqThread t;
	String governor;
	static Data data;
	static ArrayList<String> availableFreqs;
	private static int mode = 1;
	static int appCounter = 0;
	static boolean script = true;

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public void onCreate() 
	{
		//code to execute when the service is first created
		Toast.makeText(getApplicationContext(), "service created", Toast.LENGTH_SHORT).show();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startid) 
	{
		//code to execute when the service is starting up
		Notification notification = new Notification(R.drawable.icon, "ECUI",System.currentTimeMillis());
		Intent notificationIntent = new Intent(this, ECUIActivity.class);
		notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
		PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
		notification.setLatestEventInfo(this, "ECUI", "Currently running", pendingIntent);
		notification.flags|=Notification.FLAG_NO_CLEAR;
		startForeground(1337, notification);
		Toast.makeText(getApplicationContext(), "service started", Toast.LENGTH_SHORT).show();

		data = new Data();
		availableFreqs = getAvailableFreqs();
		

		//read current governor
		governor = executeRead("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");

		setGovernor("userspace");

		setFreq(getMaxFreq());

		t = new FreqThread();
		t.start();

		return (START_NOT_STICKY);
	}

	@Override
	public void onDestroy() 
	{
		//code to execute when the service is shutting down

		//stop thread
		t = null;

		//set governor back to saved governor
		setGovernor(governor);

		Toast.makeText(getApplicationContext(), "service destroyed", Toast.LENGTH_SHORT).show();
	}







	//tests for root permissions
	public static void getRoot(Context c) {
		//create process
		Process p;  
		try {  
			// Execute su to get root privileges  
			p = Runtime.getRuntime().exec("su");   

			// Attempt to write a file to a root-only  
			DataOutputStream os = new DataOutputStream(p.getOutputStream());  
			os.writeBytes("echo \"Do I have root?\" >/system/sd/temporary.txt\n");  

			// Close the terminal  
			os.writeBytes("exit\n");  
			os.flush();  
			try {  
				p.waitFor();  
				if (p.exitValue() != 255) {  
					// TODO Code to run on success  
					Toast.makeText(c, "root", Toast.LENGTH_SHORT).show();  
				}  
				else {  
					// TODO Code to run on unsuccessful  
					Toast.makeText(c, "not root", Toast.LENGTH_SHORT).show();  
				}  
			} catch (InterruptedException e) {  
				// TODO Code to run in interrupted exception  
				Toast.makeText(c, "not root", Toast.LENGTH_SHORT).show();  
			}  
		} catch (IOException e) {  
			// TODO Code to run in input/output exception  
			Toast.makeText(c, "not root", Toast.LENGTH_SHORT).show();  
		}  

	}


	//returns all available frequencies
	public static String getAvailableFreq() {
		return executeRead("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies");
	}


	//returns current frequency
	//doesn't work in emulator, so returns nanotime to make sure freq display thread is updating
	public static String getCurrentFreq() {
		String result = executeRead("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");

		if (result.length() == 0)
		{
			//for emulator testing purposes
			return Long.toString(System.nanoTime()).substring(7);
		}
		else
		{
			return result;
		}
	}

	
	
	public String[] getCurrentProcesses(){
		ActivityManager manager =  (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
		List<RunningAppProcessInfo> processes = manager.getRunningAppProcesses();

		String[] ret = new String[processes.size()];
		for (int i = 0; i < processes.size(); i++){
			ret[i] = processes.get(0).processName;
		}
		return ret;
	}


	public String getActiveProcess(){
		ActivityManager manager = (ActivityManager)this.getSystemService(Activity.ACTIVITY_SERVICE);
		String packageName = manager.getRunningTasks(1).get(0).topActivity.getPackageName();
		return packageName;
	}


	public static List<ActivityManager.RunningAppProcessInfo> getRunningApps(Context context){
		ActivityManager manager = (ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE);
		List<ActivityManager.RunningAppProcessInfo> runningApps = manager.getRunningAppProcesses();
		return runningApps;
	}


	//sets cpu governor
	public static void setGovernor(String governor)
	{
		executeSu("echo " + governor + " > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");

	}


	//called when impatience button is pressed
	public static void userIsImpatient(Context context) {
		data.addTime();
		int currFreq = Integer.parseInt(getCurrentFreq());
		Log.d(TAG, "current freq: " + currFreq);

		t.restart = true;
		Log.d(TAG, "maxFreq: " + getMaxFreq());
		t.interrupt();
		
		

		List<RunningAppProcessInfo> currApps = getRunningApps(context);

		for(RunningAppProcessInfo app: currApps){
			data.updateProcessList(app.processName, currFreq);
		}

		if(data.userIsReallyImpatient()){
			data.setDelta();
		}
		
		Log.d(TAG, data.data.processList.toString());

	}



	//sets cpu frequency
	public static String setFreq(String freq)
	{	
		executeSu("echo " + freq + " > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed");
		return freq;
	}

	
	//gets frequencies as strings for getMaxFreq
	public ArrayList<String> getAvailableFreqs()
	{
		String freq_string = executeRead("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies");
		ArrayList<String> freq_array = new ArrayList<String>();

		Scanner tokens = new Scanner(freq_string);
		while(tokens.hasNext()){
			freq_array.add(tokens.next());
		}

		return freq_array;
	}

	//returns max freq
	public static String getMaxFreq(){
		return availableFreqs.get(0);
	}

	//find fraction needed to set frequency to unavailable frequency
	//desired is in kHz!!!!
	public static String[] calculateFreq(int desiredFreq)
	{
		/* w = desired frequency
		 * x = fraction needed
		 * y = upper frequency
		 * z = lower frequency
		 * 
		 * x = (w - z) / (y - z)
		 * 
		 * ex given w = 500, y = 600, z = 200
		 * x = (500 - 200) / (600 - 200) = 300 / 400 = 3 / 4
		 */

		String retValues[] = new String[3]; //return fraction, upper, lower
		int upper = 0;
		int lower = 0;
		double timeFraction = 0;
		//DecimalFormat df = new DecimalFormat("##.###");
		boolean found = false; //flag for finding an available freq = to desired freq

		//get freqs, convert to int array
		String freqList[] = getAvailableFreq().split(" ");
		int available[] = new int[freqList.length];
		for (int i = 0; i < freqList.length; i++)
		{
			available[i] = Integer.parseInt(freqList[i]);
		}

		//find upper and lower
		for (int i = 0; i < available.length; i++)
		{
			if (available[i] == desiredFreq)
			{
				upper = desiredFreq;
				lower = desiredFreq;
				found = true;
				break;
			}
			else if (available[i] > desiredFreq)
			{
				upper = available[i];
			}
			else if (available[i] < desiredFreq)
			{
				lower = available[i];
				break;
			}
		}

		//calculate fraction
		if(found)
		{
			timeFraction = 1;
		}
		else
		{
			timeFraction = (desiredFreq - lower) / (double)(upper - lower);
		}
		retValues[0] = Double.toString(timeFraction);
		retValues[1] = Integer.toString(upper);
		retValues[2] = Integer.toString(lower);
		Log.i(TAG, retValues[0]);
		Log.i(TAG, retValues[1]);
		Log.i(TAG, retValues[2]);

		return retValues;

	}

	//executes a command needing su
	private static void executeSu(String command)
	{
		Process process;
		DataOutputStream os;

		try {
			//get root access
			process = Runtime.getRuntime().exec("su");

			//set up stream to process
			os = new DataOutputStream(process.getOutputStream());

			//send command to process
			os.writeBytes(command + "\n");
			os.flush();

			//exit process
			os.writeBytes("exit\n");
			os.flush();

			//close stream & process
			os.close();

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}   
	}


	//reads the file at a given location
	private static String executeRead(String location)
	{
		ProcessBuilder cmd;
		StringBuilder sb = null;

		try{
			String[] args = {"/system/bin/cat", location};
			cmd = new ProcessBuilder(args);

			Process process = cmd.start();
			InputStream in = process.getInputStream();

			sb = new StringBuilder();
			Scanner sc = new Scanner(in);
			while(sc.hasNextLine()){
				sb.append(sc.nextLine());
			}

			in.close();
		} catch(IOException ex){
			ex.printStackTrace();
		}
		return sb.toString();
	}

	public void displayMode() {
		Toast.makeText(getApplicationContext(), "mode " + Integer.toString(mode), Toast.LENGTH_SHORT).show();
	}
	
	
	//algorithm value switcher
	public static void setMode(String value)
	{
		if (value.equals("Light")) {
			mode = 0;
		}
		else if (value.equals("Moderate")) {
			mode = 1;
		}
		else if (value.equals("Aggressive")) {
			mode = 2;
		}
		else {
			mode = 1;
		}
	}
	
	
	//test script method
	private void runScript()
	{
		//script variables
		String appTerminal = "am start -a android.intent.action.MAIN -n ";
		String [] appArray = new String[5];
		appArray[0] = "com.android.browser/.BrowserActivity";  //browser
		appArray[1] = "com.google.android.gm/.ConversationListActivityGmail"; //gmail
		appArray[2] = "com.google.android.calendar/com.android.calendar.LaunchActivity"; //calendar
		appArray[3] = "com.google.android.apps.maps/com.google.android.maps.MapsActivity"; //maps
		appArray[4] = "com.google.android.camera/com.android.camera.Camera"; //camera
		
		
		//app running script
		switch (appCounter) {
		case 10: //app 1
			executeSu(appTerminal + appArray[0]);
			break;
		case 12: //impatience
			userIsImpatient(getApplicationContext());
			break;
		case 20: //app 2
			executeSu(appTerminal + appArray[1]);
			break;
		case 24: //impatience
			userIsImpatient(getApplicationContext());
			break;
		case 30: //app 3
			executeSu(appTerminal + appArray[2]);
			break;
		case 36: //impatience
			userIsImpatient(getApplicationContext());
			break;
		case 40: //app 4
			executeSu(appTerminal + appArray[3]);
			break;
		case 48: //impatience
			userIsImpatient(getApplicationContext());
			break;
		case 50: //app 5
			executeSu(appTerminal + appArray[4]);
			break;
		case 55: //impatience
			userIsImpatient(getApplicationContext());
			appCounter = 0;
			break;
		default:
			break;
		}
	
		
		
	}
	
	
	
	//ALGORITHM
	public class FreqThread extends Thread{
		public boolean restart;
		Looper loop;

		@Override
		public void run() 
		{
			//t.setDaemon(true);
			int curr_freq = 0;//Integer.parseInt(data.getMaxFreq());
			String[] freqdata;
			double time;
			
			setFreq(getMaxFreq());
			
			try{
				Looper.prepare();
			}catch(RuntimeException e){
				
			}
			
			restart = false;

			Thread thisThread = Thread.currentThread();

			
			int timeInterval;
			int decAmount;
			int lessDecAmount;
			boolean empty;
			int max = 0;
			int delta;
			List<RunningAppProcessInfo> currApps;

			displayMode();
			if (mode == 0) {
				timeInterval = 60000;
				decAmount = 100000;
				lessDecAmount = 25000;
			}
			else if (mode == 1) {
				timeInterval = 30000;
				decAmount = 100000;
				lessDecAmount = 25000;
			}
			else if (mode == 2) {
				timeInterval = 15000;
				decAmount = 100000;
				lessDecAmount = 25000;
			}
			else {
				timeInterval = 30000;
				decAmount = 100000;
				lessDecAmount = 25000;
			}
			
			
			
			
			
			
			
			try {
				Thread.sleep(timeInterval);
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

			while (t == thisThread)
			{
				if(data.data.delta !=0 && new Date().after(data.data.delta_stop_time)){
					data.data.delta = 0;
				}
				
				//if test script is enabled, run it
				if (script){ 
					runScript();
				}
				

				delta = data.data.delta;
				max = 0;
				empty = true;

				Log.d(TAG, "decreasing");

				currApps = getRunningApps();

				for(RunningAppProcessInfo app: currApps){
					if(!data.data.processList.containsKey(app.processName)){
						empty = true;
						break;								
					}else{						
						if(data.data.processList.get(app.processName) > max){
							max = data.data.processList.get(app.processName) + delta;
						}

						empty = false;
					}

				}

				if(empty == true){
					curr_freq = Integer.parseInt(getCurrentFreq()) - decAmount;
				}
				else{
					if(Integer.parseInt(getCurrentFreq()) > max){
						curr_freq = max;
					}
					else{
						curr_freq = Integer.parseInt(getCurrentFreq()) - lessDecAmount;
					}
				}

				freqdata = calculateFreq(curr_freq);
				time = timeInterval * Double.parseDouble(freqdata[0]);
				Toast.makeText(getApplicationContext(), "freq decreased", Toast.LENGTH_LONG).show();

				if(freqdata[0].equals("1")){
					Log.d(TAG, "freqdata[0].equals(\"1\")");
					executeSu("echo " + freqdata[1] + " > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed");
					try {
						Thread.sleep(timeInterval);
					} catch (InterruptedException e) {
						if(t != thisThread) break;
					}
				}
				else{
					Log.d(TAG, "alternate");
					executeSu("echo " + freqdata[1] + " > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed");

					try {
						Thread.sleep((long) time);
					} catch (InterruptedException e) {
						if(restart == true) break;
					}

					executeSu("echo " + freqdata[2] + " > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed");

					try {
						Thread.sleep(timeInterval - (long) time);
					} catch (InterruptedException e) {
						if(restart == true){
							
						}
					}
				}


				if (script) {
					appCounter++;
					Toast.makeText(getApplicationContext(), Integer.toString(appCounter), Toast.LENGTH_SHORT).show();
				}

			}
			
			
			this.run();
		}

		private List<RunningAppProcessInfo> getRunningApps() {
			ActivityManager manager = (ActivityManager)getApplicationContext().getSystemService(Activity.ACTIVITY_SERVICE);
			List<ActivityManager.RunningAppProcessInfo> runningApps = manager.getRunningAppProcesses();
			return runningApps;
		}

	}
}